﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Lesson13_异步_服务端综合练习
{
    internal class ClientSocket
    {
        public Socket socket;
        public int clientID;
        private static int CLIENT_BEGIN_ID = 1;

        private byte[] cacheBytes = new byte[1024];
        private int cacheLen = 0;

        //心跳时间
        private long HeartTime = 0;
        //超时时间
        private int TIME_OUT_SECOND = 5;

        public ClientSocket(Socket socket)
        {
            this.clientID = CLIENT_BEGIN_ID++;
            this.socket = socket;

            //开始收消息
            this.socket.BeginReceive(cacheBytes, cacheLen, cacheBytes.Length, SocketFlags.None, RecviveCallBack, null);
            ThreadPool.QueueUserWorkItem(CheckTimeOut);
        }

        //检测超时
        private void CheckTimeOut(object obj)
        {
            while (this.socket != null && this.socket.Connected)
            {
                Thread.Sleep(5000);
                if (HeartTime == 0)
                    continue;
                long nowTime = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;
                if (nowTime - HeartTime >= TIME_OUT_SECOND)
                {
                    Program.socket.CloseClientSocket(this);
                    break;
                }
            }
        }

        private void RecviveCallBack(IAsyncResult result)
        {
            try
            {
                if (this.socket != null && this.socket.Connected)
                {
                    //返回值：收到了多少个字节
                    int num = this.socket.EndReceive(result);
                    //接收长度为0时表示客户端强退
                    if(num == 0)
                    {
                        Program.socket.CloseClientSocket(this);
                        return;
                    }
                    HandleReceiveMsg(num);
                    //继续接收消息
                    if (this.socket.Connected)
                        this.socket.BeginReceive(cacheBytes, cacheLen, cacheBytes.Length - cacheLen, SocketFlags.None, RecviveCallBack, null);
                }
                else
                {
                    Program.socket.CloseClientSocket(this);
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine("[ClientSocket][RecviveCallBack] error code:{0}", e.SocketErrorCode);
            }
        }

        public void Send(BaseMsg msg)
        {
            try
            {
                if (socket != null && socket.Connected)
                {
                    byte[] bytes = msg.Writing();
                    this.socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallBack, null);
                }
                else
                {
                    Console.WriteLine("没有连接，不用再收消息了");
                    Program.socket.CloseClientSocket(this);
                }
            }
            catch (Exception e)
            {
                Program.socket.CloseClientSocket(this);
                Console.WriteLine("[ClientSocket][Send] Fail(Id:{0} Error:{1})", clientID, e.Message);
            }
        }

        private void SendCallBack(IAsyncResult result)
        {
            try
            {
                if (socket != null && socket.Connected)
                    this.socket.EndSend(result);
                else
                    Program.socket.CloseClientSocket(this);
            }
            catch (SocketException e)
            {
                Console.WriteLine("[ClientSocket][SendCallBack] error code:{0}", e.SocketErrorCode);
            }
        }

        public void Close()
        {
            if (socket == null)
                return;
            Console.WriteLine("[ClientSocket][Close] Clinet:{0}!", this.clientID);
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
            socket = null;
        }

        private void HandleReceiveMsg(int recvLen)
        {
            try
            {
                int msgID = 0;
                int msgLen = 0;
                int nowIndex = 0;

                cacheLen += recvLen;

                while (true)
                {
                    //根据-1判断是否解析了包头
                    msgLen = -1;
                    if (cacheLen - nowIndex >= 8)
                    {
                        //解析ID
                        msgID = BitConverter.ToInt32(cacheBytes, 0);
                        nowIndex += 4;
                        //解析长度
                        msgLen = BitConverter.ToInt32(cacheBytes, nowIndex);
                        nowIndex += 4;
                    }
                    //排队消息头,剩余长度>=消息长度,表示至少有一个可解析的消息体
                    if (cacheLen - nowIndex >= msgLen && msgLen != -1)
                    {
                        //解析消息体
                        BaseMsg baseMsg = null;
                        switch (msgID)
                        {

                            case 999:
                                {
                                    baseMsg = new HeartMsg();
                                    break;
                                }
                            case 1001:
                                {
                                    baseMsg = new RoleMsg();
                                    baseMsg.Reading(cacheBytes, nowIndex);
                                    break;
                                }
                            case 1003:
                                {
                                    baseMsg = new QuitMsg();
                                    break;
                                }
                        }
                        if (baseMsg != null)
                            ThreadPool.QueueUserWorkItem(MsgHandle, baseMsg);
                        nowIndex += msgLen;
                        if (nowIndex == cacheLen)
                        {
                            cacheLen = 0;
                            break;
                        }
                    }
                    else
                    {
                        //如果进行了 id和长度的解析 但是没有成功解析消息体 需要减去nowIndex移动的位置
                        if (msgLen != -1)
                            nowIndex -= 8;
                        //将没有解析出来的数据重新移到缓存从0开始的位置 下次继续解析
                        Array.Copy(cacheBytes, nowIndex, cacheBytes, 0, cacheLen - nowIndex);
                        cacheLen = cacheLen - nowIndex;
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ClientSocket][HandleReceiveMsg] 处理消息异常: {ex.Message}");
                cacheLen = 0; // 发生异常时清空缓存
            }
        }
        //消息处理
        private void MsgHandle(object obj)
        {
            try
            {
                BaseMsg msg = obj as BaseMsg;
                if (msg is HeartMsg)
                {
                    HeartTime = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;
                    Console.WriteLine("客户端{0}心跳.", clientID);
                }
                else if (msg is RoleMsg)
                {
                    Console.WriteLine("接收到客户端[{0}]消息:", clientID);
                    (msg as RoleMsg).Print();
                }
                else if (msg is QuitMsg)
                {
                    Program.socket.CloseClientSocket(this);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("[ClientSocket][MsgHandle] Fail(Id:{0} Error:{1})", clientID, e.Message);
            }
        }
    }
}
